package de.lengnis.games.brickdrop.model;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JComponent;

import de.lengnis.games.brickdrop.event.SpielfeldEventListener;
import de.lengnis.games.brickdrop.factory.BrickFactory;

@SuppressWarnings("serial")
public class Spielfeld extends JComponent implements KeyListener {

    /* WIDHT und HEIGHT müssen durch 20 ohne Rest teilbar sein */
    public static final int              WIDTH               = 260;
    public static final int              HEIGHT              = 400;

    public static final int              SCHUB_BLOCKIER_WERT = 30;

    private List<Brick>                  bricks              = new ArrayList<Brick>();

    private Brick                        aktiverBrick;
    private Brick                        naechsterBrick;

    private BrickFallThread              fallThread;
    private boolean                      isFallThreadAktiv   = false;

    /** Fallgeschwindigkeit in ms. */
    private int                          fallGeschwindigkeit = 500;

    private List<SpielfeldEventListener> listenerList        = new ArrayList<SpielfeldEventListener>();

    public Spielfeld() {

        setPreferredSize(new Dimension(WIDTH, HEIGHT));
        setBorder(BorderFactory.createLineBorder(Color.BLACK));

        addKeyListener(this);

        starteFallThread();
    }

    public void starteFallThread() {

        if (fallThread == null) {

            fallThread = new BrickFallThread();

            isFallThreadAktiv = true;

            fallThread.start();
        }
    }

    public void stoppeFallThread() {

        if (fallThread != null) {

            isFallThreadAktiv = false;

            fallThread = null;
        }
    }

    public void neuerBrick() {

        Brick neuerBrick = BrickFactory.getZufallsBrick();

        if (naechsterBrick == null)
            naechsterBrick = BrickFactory.getZufallsBrick();

        if (isUeberschneidung(naechsterBrick, Seite.UNTEN)) {
            stoppeFallThread();
            return;
        }

        bricks.add(naechsterBrick);

        naechsterBrick.zentrieren(WIDTH, 0, SchrittweiteTyp.GROB);

        aktiverBrick = naechsterBrick;
        naechsterBrick = neuerBrick;

        fireSpielfeldChangedEvent();
    }

    /**
     * Prüft, ob der übergebene Brick sich mit einem anderen Brick
     * überschneidet.
     */
    public boolean isUeberschneidung(Brick brick, Seite seite) {

        if (bricks.isEmpty())
            return false;

        List<TeilBrick> alleTeilBricks = new ArrayList<TeilBrick>();

        for (Brick brickAufSpielfeld : bricks) {

            if (brickAufSpielfeld.equals(brick))
                continue;

            alleTeilBricks.addAll(brickAufSpielfeld.getTeilBricks());
        }

        for (TeilBrick aktiverTeilBrick : brick.getTeilBricks())
            for (TeilBrick teilBrick : alleTeilBricks) {

                boolean isGleicheHoeheVertikal = aktiverTeilBrick.getX() == teilBrick.getX();
                boolean isGleicheHoeheHorizontal = aktiverTeilBrick.getY() == teilBrick.getY();

                if (seite == Seite.UNTEN && isGleicheHoeheVertikal && aktiverTeilBrick.getMaxY() == teilBrick.getY())
                    return true;
                else if (seite == Seite.RECHTS && isGleicheHoeheHorizontal && aktiverTeilBrick.getMaxX() == teilBrick.getX())
                    return true;
                else if (seite == Seite.LINKS && isGleicheHoeheHorizontal && aktiverTeilBrick.getX() == teilBrick.getMaxX())
                    return true;
            }

        return false;
    }

    /**
     * Prüft, ob der aktive Brick das untere Ende des Spielfeldes erreicht hat.
     */
    public boolean isGanzUnten() {

        for (TeilBrick teilBrick : aktiverBrick.getTeilBricks())
            if (teilBrick.getMaxY() >= HEIGHT)
                return true;

        return false;
    }

    public void pruefeAufVolleReihen() {

        for (int y = 0; y < HEIGHT; y += TeilBrick.SEITENLAENGE) {

            List<TeilBrick> teilBricks = getTeilBricksInHoehe(y);

            if (teilBricks.isEmpty())
                continue;

            boolean istVoll = true;

            for (int x = 0; x < WIDTH; x += TeilBrick.SEITENLAENGE) {

                boolean hatStein = false;

                for (TeilBrick teilBrick : teilBricks)
                    if (teilBrick.getX() == x) {
                        hatStein = true;
                        break;
                    }

                if (!hatStein)
                    istVoll = false;
            }

            if (istVoll) {
                entferneTeilBricks(y);
                verschiebeTeilBricksNachUnten(getAlleTeilBricks(), y);
            }
        }
    }

    public List<TeilBrick> getAlleTeilBricks() {

        List<TeilBrick> teilBricks = new ArrayList<TeilBrick>();

        for (Brick brick : bricks)
            for (TeilBrick teilBrick : brick.getTeilBricks())
                teilBricks.add(teilBrick);

        return teilBricks;
    }

    public List<TeilBrick> getTeilBricksInHoehe(int y) {

        List<TeilBrick> teilBricks = new ArrayList<TeilBrick>();

        for (Brick brick : bricks)
            for (TeilBrick teilBrick : brick.getTeilBricks())
                if (teilBrick.getY() == y)
                    teilBricks.add(teilBrick);

        return teilBricks;
    }

    public void entferneTeilBricks(int y) {

        for (Brick brick : bricks) {

            Iterator<TeilBrick> it = brick.getTeilBricks().iterator();

            while (it.hasNext())
                if (it.next().getY() == y)
                    it.remove();
        }
    }

    public void verschiebeTeilBricksNachUnten(List<TeilBrick> teilBricks, int minY) {

        for (TeilBrick teilBrick : teilBricks) {

            if (teilBrick.getY() >= minY)
                continue;

            teilBrick.bewegen(0, TeilBrick.SEITENLAENGE);
        }
    }

    public void addSpielfeldEventListener(SpielfeldEventListener listener) {
        listenerList.add(listener);
    }

    public void fireSpielfeldChangedEvent() {
        for (SpielfeldEventListener listener : listenerList)
            listener.spielfeldChanged();
    }

    @Override
    protected void paintComponent(Graphics g) {

        Graphics2D g2 = (Graphics2D) g;

        /* Schwarzer Hintergrund */
        g2.setColor(Color.BLACK);
        g2.fillRect(0, 0, WIDTH, HEIGHT);

        /* Raster */
        maleRaster(g2, WIDTH, HEIGHT);

        /* Steine */
        for (Brick brick : bricks)
            brick.malen(g2);

        requestFocus();
    }

    private class BrickFallThread extends Thread {

        @Override
        public void run() {

            while (isFallThreadAktiv) {

                if (aktiverBrick == null)
                    neuerBrick();

                if (isUeberschneidung(aktiverBrick, Seite.UNTEN) || isGanzUnten()) {

                    pruefeAufVolleReihen();

                    neuerBrick();

                } else
                    aktiverBrick.bewegen(0, TeilBrick.SEITENLAENGE);

                repaint();

                try {
                    sleep(fallGeschwindigkeit);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void keyTyped(KeyEvent e) {

    }

    public void keyPressed(KeyEvent e) {

        if (!isFallThreadAktiv)
            return;

        if (e.getKeyCode() == KeyEvent.VK_LEFT
                && aktiverBrick.getMinX() > 0
                && !isUeberschneidung(aktiverBrick, Seite.LINKS)) {

            aktiverBrick.bewegen(-TeilBrick.SEITENLAENGE, 0);
        }

        if (e.getKeyCode() == KeyEvent.VK_RIGHT
                && aktiverBrick.getMaxX() < WIDTH
                && !isUeberschneidung(aktiverBrick, Seite.RECHTS)) {

            aktiverBrick.bewegen(TeilBrick.SEITENLAENGE, 0);
        }

        if (e.getKeyCode() == KeyEvent.VK_UP) {

            aktiverBrick.drehen();

            if (aktiverBrick.getMaxX() > (getX() + getWidth()))
                aktiverBrick.bewegen(-TeilBrick.SEITENLAENGE, 0);

            if (aktiverBrick.getMinX() < getX())
                aktiverBrick.bewegen(TeilBrick.SEITENLAENGE, 0);
        }

        if (e.getKeyCode() == KeyEvent.VK_DOWN
                && !isGanzUnten()
                && !isUeberschneidung(aktiverBrick, Seite.UNTEN)
                && aktiverBrick.getMinY() > SCHUB_BLOCKIER_WERT) {

            aktiverBrick.bewegen(0, TeilBrick.SEITENLAENGE);
        }

        if (e.getKeyCode() == KeyEvent.VK_SPACE)
            while (!isGanzUnten() && !isUeberschneidung(aktiverBrick, Seite.UNTEN))
                aktiverBrick.bewegen(0, TeilBrick.SEITENLAENGE);

        repaint();
    }

    public void keyReleased(KeyEvent e) {

    }

    public Brick getNaechsterBrick() {
        return naechsterBrick;
    }

    public static void maleRaster(Graphics2D g2, int width, int height) {

        g2.setColor(Color.DARK_GRAY.darker().darker().darker());

        for (int x = 0; x < width; x += TeilBrick.SEITENLAENGE)
            for (int y = 0; y < height; y += TeilBrick.SEITENLAENGE) {
                g2.drawLine(x, y, x, y + TeilBrick.SEITENLAENGE);
                g2.drawLine(x, y, x + TeilBrick.SEITENLAENGE, y);
            }
    }
}
